package org.example.blog.services.impl;

import com.google.gson.Gson;
import com.wf.captcha.ArithmeticCaptcha;
import com.wf.captcha.GifCaptcha;
import com.wf.captcha.SpecCaptcha;
import com.wf.captcha.base.Captcha;
import io.jsonwebtoken.Claims;
import lombok.extern.slf4j.Slf4j;
import org.example.blog.dao.RefreshTokenDao;
import org.example.blog.dao.SettingDao;
import org.example.blog.dao.UserDao;
import org.example.blog.pojo.RefreshToken;
import org.example.blog.pojo.Setting;
import org.example.blog.pojo.User;
import org.example.blog.response.ResponseResult;
import org.example.blog.response.ResponseState;
import org.example.blog.services.IUserService;
import org.example.blog.utils.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.util.DigestUtils;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.transaction.Transactional;
import javax.xml.crypto.dsig.DigestMethod;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;


@Slf4j
@Service
@Transactional
public class UserServiceImpl implements IUserService {

    @Autowired
    private IdWorker idWorker;

    @Autowired
    private BCryptPasswordEncoder bCryptPasswordEncoder;

    @Autowired
    private UserDao userDao;

    @Autowired
    private SettingDao settingDao;

    @Autowired
    private RedisUtils redisUtils;

    @Autowired
    private Random random;

    @Autowired
    private RefreshTokenDao refreshTokenDao;

    @Autowired
    private Gson gson;

    public static final int[] captcha_font_types = {Captcha.FONT_1
            , Captcha.FONT_2
            , Captcha.FONT_3
            , Captcha.FONT_4
            , Captcha.FONT_5
            , Captcha.FONT_6
            , Captcha.FONT_7
            , Captcha.FONT_8
            , Captcha.FONT_9
            , Captcha.FONT_10};

    @Override
    /**
     * 初始化管理员
     *
     * @param user
     * @param request
     * @return org.example.blog.response.ResponseResult
     */
    public ResponseResult initManagerAccount(User user, HttpServletRequest request) {
        // 检查是否初始化
        Setting managerAccountState = settingDao.findOneByKey(Constants.Setting.MANAGER_ACCOUNT_INIT_STATE);
        if (managerAccountState != null) {
            return ResponseResult.FAILED("账号已经初始化");
        }

        // 检查数据
        if (TextUtils.isEmpty(user.getUsername())) {
            return ResponseResult.FAILED("用户名不为空");
        }
        if (TextUtils.isEmpty(user.getPassword())) {
            return ResponseResult.FAILED("密码不为空");
        }
        if (TextUtils.isEmpty(user.getEmail())) {
            return ResponseResult.FAILED("邮箱不为空");
        }
        // 补充数据
        user.setId(String.valueOf(idWorker.nextId()));
        user.setRoles(Constants.User.ROLE_ADMIN);
        user.setAvatar(Constants.User.DEFAULT_AVATAR);
        user.setState(Constants.User.DEFAULT_STATE);

        String remoteAddr = request.getRemoteAddr();
        String localAddr = request.getLocalAddr();
        log.info("remoteAddr ====> " + remoteAddr);
        log.info("localAddr ====> " + localAddr);
        user.setLoginIp(remoteAddr);
        user.setRegIp(remoteAddr);
        user.setCreateTime(new Date());
        user.setUpdateTime(new Date());
        // 对密码进行加密
        // 原密码
        String password = user.getPassword();
        // 加密后的密码
        String encode = bCryptPasswordEncoder.encode(password);
        user.setPassword(encode);

        // 保存到数据库里
        userDao.save(user);


        // 更新已经添加的标记
        Setting setting = new Setting();
        setting.setId(idWorker.nextId() + "");
        setting.setCreateTime(new Date());
        setting.setUpdateTime(new Date());
        setting.setKey(Constants.Setting.MANAGER_ACCOUNT_INIT_STATE);
        setting.setValue("1");
        settingDao.save(setting);
        return ResponseResult.SUCCESS("初始化管理员成功");
    }


    @Override
    /**
     * 生成图灵验证码
     * @param response
     * @param captchaKey
     * @return void
     */
    public void createCaptcha(HttpServletResponse response, String captchaKey) throws Exception {
        // 如果为空或者长度小于13 停止操作
        if (TextUtils.isEmpty(captchaKey) || captchaKey.length() < 13) {
            return;
        }
        long key = 0l;
        try {
            key = Long.parseLong(captchaKey);
        } catch (Exception e) {
            return;
        }
        // 可以用了

        // 设置请求头为输出图片类型
        response.setContentType("image/gif");
        response.setHeader("Pragma", "No-cache");
        response.setHeader("Cache-Control", "no-cache");
        response.setDateHeader("Expires", 0);

        // 随机选择验证码是否是运算或者其他类型
        int captchaType = random.nextInt(3);
        Captcha targetCaptcha = null;

        if (captchaType == 0) {
            // 三个参数分别为宽、高、位数
            targetCaptcha = new SpecCaptcha(200, 60, 5);
        } else if (captchaType == 1) {
            // gif类型
            targetCaptcha = new GifCaptcha(200, 60);
        } else {
            // 算术类型
            targetCaptcha = new ArithmeticCaptcha(200, 60);
            targetCaptcha.setLen(2);  // 几位数运算，默认是两位
        }

        String content = targetCaptcha.text().toLowerCase();
        log.info("captcha content == > " + content);

//        随机选择验证码字体
        int index = random.nextInt(captcha_font_types.length);
        log.info("index ======= > " + index);
        targetCaptcha.setFont(captcha_font_types[index]);
        targetCaptcha.setCharType(Captcha.TYPE_DEFAULT);
        // 保存到Redis
        redisUtils.set(Constants.User.KEY_CAPTCHA_CONTENT + key, content, 60 * 10);
        // 输出图片流
        targetCaptcha.out(response.getOutputStream());
    }


    @Autowired
    private TaskService taskService;

    @Override
    /**
     *
     * 发送邮箱验证码
     *
     * 业务使用场景：注册、找回密码、修改邮箱（会输入新的邮箱）
     * 注册(register)：如果已经注册过了，提示该邮箱已经注册
     * 找回密码(forget)：如果没有注册，提示该邮箱没有注册
     * 修改邮箱(update)（新的邮箱）：如果已经注册，提示该邮箱已经注册
     *
     * @param request
     * @param emailAddress
     * @return org.example.blog.response.ResponseResult
     */
    public ResponseResult sendEmail(HttpServletRequest request, String type, String emailAddress) {
        if (emailAddress == null) {
            return ResponseResult.FAILED("邮箱地址不能为空");
        }

        // 判断类型 验证邮箱是否存在
        if ("register".equals(type) || "update".equals(type)) {
            User userByEmail = userDao.findOneByEmail(emailAddress);
            if (userByEmail != null) {
                return ResponseResult.FAILED("该邮箱已经注册");
            }
        } else if ("forget".equals(type)) {
            User userByEmail = userDao.findOneByEmail(emailAddress);
            if (userByEmail == null) {
                return ResponseResult.FAILED("该邮箱没有注册");
            }
        }


        // 1、防止暴力发送邮箱（不断发送）：同一个邮箱，间隔超过30秒发一次，同一个IP，1小时内最多只能发10次。（如果是短信，最多5次）
        String remoteAddr = request.getRemoteAddr();
        log.info("remoteAddr ======= > " + remoteAddr);

        if (remoteAddr != null) {
            remoteAddr = remoteAddr.replaceAll(":", "_");
        }

        //拿出来 如果没有 那就跳过
        Integer ipSendTime = (Integer) redisUtils.get(Constants.User.kEY_EMAIL_SEND_IP + remoteAddr);

        if (ipSendTime != null && ipSendTime > 10) {
            return ResponseResult.FAILED("您发送验证码太过频繁");
        }

        Object hasEmailSend = redisUtils.get(Constants.User.kEY_EMAIL_SEND_ADDRESS + emailAddress);
        if (hasEmailSend != null) {
            return ResponseResult.FAILED("您发送验证码太过频繁");
        }
        //如果有 判断次数

        // 2、检查邮箱地址是否正确
        boolean isEmailFormatOK = TextUtils.isEmailAddressOk(emailAddress);
        if (!isEmailFormatOK) {
            return ResponseResult.FAILED("邮箱地址不正确");
        }

        // 3、发送验证码, 6位 100000 - 999999
        int code = random.nextInt(999999);
        if (code < 100000) {
            code += 100000;
        }
        log.info("sendEmailCode =====> " + code);

        try {
            taskService.sendEmailVerifyCode(String.valueOf(code), emailAddress);
        } catch (Exception e) {
            return ResponseResult.FAILED("验证码发送失败，请稍后重试");
        }

        // 4、做记录
        // 发送记录 code
        //
        if (ipSendTime == null) {
            ipSendTime = 0;
        }
        ipSendTime++;
        // Ip 一个小时最多发10次
        redisUtils.set(Constants.User.kEY_EMAIL_SEND_IP + remoteAddr, ipSendTime, 60 * 60);
        // 验证码间隔30秒
        redisUtils.set(Constants.User.kEY_EMAIL_SEND_ADDRESS + emailAddress, "true", 30);

        // 保存code 十分钟有效
        redisUtils.set(Constants.User.KEY_EMAIL_CODE_CONTENT + emailAddress, String.valueOf(code), 60 * 10);

        return ResponseResult.SUCCESS("验证码发送成功");
    }


    @Override
    /**
     *
     * 用户注册
     *
     * @param user
     * @return org.example.blog.response.ResponseResult
     */
    public ResponseResult register(User user, String emailCode, String captchaCode, String captchaKey, HttpServletRequest request) {
        // 第一步：检查当前用户名是否已经注册
        String userByName = user.getUsername();

        if (TextUtils.isEmpty(userByName)) {
            return ResponseResult.FAILED("用户名不能为空");
        }
        User userFromDbByUserName = userDao.findOneByUsername(userByName);

        if (userFromDbByUserName != null) {
            return ResponseResult.FAILED("该用户已经注册");
        }

        // 第二部：检查邮箱格式是否正确
        String email = user.getEmail();
        if (TextUtils.isEmpty(email)) {
            return ResponseResult.FAILED("邮箱地址不能为空");
        }
        if (!TextUtils.isEmailAddressOk(email)) {
            return ResponseResult.FAILED("邮箱格式不正确");
        }

        // 第三步：检查该邮箱是否已经注册
        User userByEmail = userDao.findOneByEmail(email);
        if (userByEmail != null) {
            return ResponseResult.FAILED("该邮箱已经注册");
        }

        // 第四步：检查邮箱验证码是否正确
        String emailVerifyCode = (String) redisUtils.get(Constants.User.KEY_EMAIL_CODE_CONTENT + email);
        log.info(email);
        log.info("邮箱比对  ======》" + emailVerifyCode);
        if (TextUtils.isEmpty(emailVerifyCode)) {
            return ResponseResult.FAILED("邮箱验证码无效");
        }

        if (!emailVerifyCode.equals(emailCode)) {
            return ResponseResult.FAILED("邮箱验证码不正确");
        } else {
            // 正确 去除redis对应内容
            redisUtils.del(Constants.User.KEY_EMAIL_CODE_CONTENT + email);
        }

        // 第五步：检查图灵验证码是否正确
        String captchaVerifyCode = (String) redisUtils.get(Constants.User.KEY_CAPTCHA_CONTENT + captchaKey);
        if (TextUtils.isEmpty(captchaVerifyCode)) {
            return ResponseResult.FAILED("图灵验证码已经过期");
        }

        if (!captchaVerifyCode.equals(captchaCode)) {
            return ResponseResult.FAILED("图灵验证码不正确");
        } else {
            redisUtils.del(Constants.User.KEY_CAPTCHA_CONTENT + captchaKey);
        }


        // 第六步：对密码加密
        String password = user.getPassword();
        if (TextUtils.isEmpty(password)) {
            return ResponseResult.FAILED("密码不能为空");
        }
        user.setPassword(bCryptPasswordEncoder.encode(user.getPassword()));

        // 第七步：补全数据
        // 包括：注册IP，登录IP，角色，头像，创建时间，更新时间
        String ipAddress = request.getRemoteAddr();
        user.setRegIp(ipAddress);
        user.setLoginIp(ipAddress);
        user.setUpdateTime(new Date());
        user.setCreateTime(new Date());
        user.setAvatar(Constants.User.DEFAULT_AVATAR);
        user.setRoles(Constants.User.ROLE_NORMAL);
        user.setId(idWorker.nextId() + "");
        user.setState("1");

        // 第八步：保存到数据库中
        userDao.save(user);

        // 第九步：返回结果
        return ResponseResult.Get(ResponseState.JOIN_IN_SUCCESS);
    }


    @Override
    public ResponseResult doLogin(String captcha,
                                  String captchaKey,
                                  User user,
                                  HttpServletResponse response) {
        String captchaValue = (String) redisUtils.get(Constants.User.KEY_CAPTCHA_CONTENT + captchaKey);

        if (!captcha.equals(captchaValue)) {
            return ResponseResult.FAILED("人类验证码不正确");
        }

        String username = user.getUsername();
        String password = user.getPassword();
        System.out.println(username);
        if (TextUtils.isEmpty(username)) {
            return ResponseResult.FAILED("账号不能为空");
        }
        if (TextUtils.isEmpty(password)) {
            return ResponseResult.FAILED("密码不能为空");
        }

        User userFromDb = userDao.findOneByUsername(username);
//        逻辑：先查找账号是否为空，再查找邮箱是否为空 都为空 账号不存在。之后再去判断密码
        if (userFromDb == null) {
            userFromDb = userDao.findOneByEmail(username);
        }

        if (userFromDb == null) {
            return ResponseResult.FAILED("用户名或者密码不正确");
        }

        // 用户存在
        // 对比密码
        boolean matches = bCryptPasswordEncoder.matches(password, userFromDb.getPassword());
        if (!matches) {
            return ResponseResult.FAILED("用户名或者密码不正确");
        }

        // 密码正确
        // 判断用户状态 ，如果是非正常，则返回结果
        if (!"1".equals(userFromDb.getState())) {
            return ResponseResult.ACCOUNT_NOT_DENIED();
        }

        createToken(response, userFromDb);
        return ResponseResult.SUCCESS("登录成功");
    }

    /**
     * 封装深层次创建token的方法
     *
     * @param response
     * @param userFromDb
     * @return token_key
     */
    private String createToken(HttpServletResponse response, User userFromDb) {
        // 删掉refreshToken记录
        int deleteResult = refreshTokenDao.deleteAllByUserId(userFromDb.getId());
        log.info("deleteResult " +deleteResult);
        // 生成token
        Map<String, Object> claims = ClaimsUtils.userClaims(userFromDb);


        // token 默认有效两小时
        String token = JwtUtil.createToken(claims);
        // 返回token的md5值 token保存在redis里
        // 前端访问的时候，是携带md5加密的token的key，拿这个key到redis里获取
        String tokenKey = DigestUtils.md5DigestAsHex(token.getBytes());

        // 保存token到redis里。key是tokenKey,有效期2小时，key是tokenKey
        redisUtils.set(Constants.User.KEY_TOKEN + tokenKey, token, Constants.TimeValue.HOUR_2);

        // 把tokenKey写到cookie里Ò
//        Cookie cookie = new Cookie(Constants.User.COOKIE_TOKEN_KEY, tokenKey);
        // 动态获取 从request获取
        // 这里需要动态设置-- 工具类
        CookieUtils.setUpCookie(response, Constants.User.COOKIE_TOKEN_KEY, tokenKey);

        // 生成refreshToken
        String refreshTokenValue = JwtUtil.createRefreshToken(userFromDb.getId(), Constants.TimeValue.MONTH);
        // 保存到数据库
        // refreshToken , tokenKey, 用户ID, 创建时间, 更新时间
        RefreshToken refreshToken = new RefreshToken();
        refreshToken.setId(idWorker.nextId() + "");
        refreshToken.setUserId(userFromDb.getId());
        refreshToken.setTokenKey(tokenKey);
        refreshToken.setRefreshToken(refreshTokenValue);
        refreshToken.setCreateTime(new Date());
        refreshToken.setUpdateTime(new Date());
        refreshTokenDao.save(refreshToken);
        return tokenKey;
    }

    private User parseByTokenKey(String tokenKey) {
        String token = (String) redisUtils.get(Constants.User.KEY_TOKEN + tokenKey);

        if (token != null) {
            try {
                Claims claims = JwtUtil.parseJWT(token);
                return ClaimsUtils.claimsUser(claims);
            } catch (Exception e) {
                return null;
            }
        }
        return null;
    }


    /**
     * 本质：通过用户携带的token_key检查是否登录，如果登录，返回用户信息。
     *
     * @param request
     * @param response
     * @return org.example.blog.pojo.User
     */
    @Override
    public User checkUser(HttpServletRequest request, HttpServletResponse response) {
        // 拿到token_key
        String tokenKey = CookieUtils.getCookie(request, Constants.User.COOKIE_TOKEN_KEY);

        User user = parseByTokenKey(tokenKey);

        if (user == null) {
            // 说明有token，解析token
            // 解析出错 过期了
            // 1.先去mysql查询refreshToken
            RefreshToken refreshToken = refreshTokenDao.findOneByTokenKey(tokenKey);
            System.out.println("refreshToken" + refreshToken);
            // 2.如果不存在 就是当前用户没有登录 提示用户登录
            if (refreshToken == null) {
                return null;
            }

            // 3.如果存在，就解析refreshToken
            try {
                JwtUtil.parseJWT(refreshToken.getRefreshToken());
                // 5.如果refreshToken有效，创建新的token，和新的refreshToken
                String userId = refreshToken.getUserId();
                User userFromDb = userDao.findOneById(userId);

                String newTokenKey = createToken(response, userFromDb);
                // 返回一个token
               return parseByTokenKey(newTokenKey);
            } catch (Exception e1) {
                // 6.如果refreshToken过期了，就当前访问没有登录，提示用户登录
                return null;
            }

        }
        return user;
    }



    @Override
    public ResponseResult getUserInfo(String userId) {
        // 从数据库里获取
        User user = userDao.findOneById(userId);
        // 判断结果
        if (user == null) {
            // 如果不存在 返回不存在
            return ResponseResult.FAILED("用户不存在");
        }

        // 如果存在 复制对象 清空密码、email、登录注册IP
        // 不能直接setPassword 需要复制出一个对象 因为jpa特性的原因  此时还跟数据库同步 需要把这个对象弄成游离态
        String userJson = gson.toJson(user);
        User newUser = gson.fromJson(userJson, User.class);
        newUser.setPassword("");
        newUser.setEmail("");
        newUser.setLoginIp("");
        newUser.setRegIp("");
        // 返回结果 end

        return ResponseResult.SUCCESS("获取用户信息成功").setData(newUser);
    }



    @Override
    public ResponseResult checkEmail(String email) {
        User user = userDao.findOneByEmail(email);
        return user == null ? ResponseResult.FAILED("该邮箱未注册。") : ResponseResult.SUCCESS("该邮箱已经注册。");
    }

    @Override
    public ResponseResult checkUserName(String userName) {
        User user = userDao.findOneByUsername(userName);
        return user == null ? ResponseResult.FAILED("该用户未注册。") : ResponseResult.SUCCESS("该用户已经注册。");

    }

    @Override
    public ResponseResult updateUserInfo(HttpServletRequest request, HttpServletResponse response, String userId, User user) {
        // 从Token里解析出来的  为了较验权限
        // 只有用户才可以修改自己信息
        User userFromTokenKey = checkUser(request, response);
        if (userFromTokenKey == null) {
            return ResponseResult.ACCOUNT_NOT_LOGIN();
        }

        User userFromDb = userDao.findOneById(userFromTokenKey.getId());

        // 判断用户的ID是否一致，一致才可以修改
        if (!userFromDb.getId().equals(userId)) {
            return ResponseResult.PERMISSION_DENIED();
        }
        // 可以进行修改
        // 可以修改的项

        // 用户名
        String userName = user.getUsername();
        if (!TextUtils.isEmpty(userName)) {
            User userByUserName = userDao.findOneByUsername(userName);
            if (userByUserName != null) {
                return ResponseResult.FAILED("该用户名已经被使用！");
            }
            userFromDb.setUsername(userName);
        }
        // 头像
        if (!TextUtils.isEmpty(user.getAvatar())) {
            userFromDb.setAvatar(user.getAvatar());
        }
        // 签名, 可以为空
        userFromDb.setSign(user.getSign());

        userDao.save(userFromDb);

        // 干掉redis的token，下一次请求要是有需要解析token的，会根据refreshToken重新创建
        String tokenKey = CookieUtils.getCookie(request, Constants.User.COOKIE_TOKEN_KEY);
        redisUtils.del(tokenKey);
        return ResponseResult.SUCCESS("更新用户信息成功！");
    }


    /**
     *
     * 删除用户，并不是真的删除
     * 而是修改状态
     * ps:需要管理员权限
     *
     * @param userId
     * @param request
     * @param response
     * @return
     */
    @Override
    public ResponseResult deleteUserById(String userId, HttpServletRequest request, HttpServletResponse response) {

        // 可以删除用户了
        int result = userDao.deleteUserByState(userId);
        if (result > 0) {
            return ResponseResult.SUCCESS("删除成功");
        }
        return ResponseResult.FAILED("用户不存在");
    }


    /**
     * 获取用户列表
     * 权限：管理员权限
     *
     * @param page
     * @param size
     * @param request
     * @param response
     * @return
     */
    @Override
    public ResponseResult listUsers(int page, int size, HttpServletRequest request, HttpServletResponse response) {

        // 可以获取用户列表
        // 分页查询
        if (page < Constants.Page.DEFAULT_PAGE) {
            page = Constants.Page.DEFAULT_PAGE;
        }
        //size 也限制一下 每页不少于5个
        if (size < Constants.Page.MIN_SIZE) {
            size = Constants.Page.MIN_SIZE;
        }

        // 根据注册日期来排序
        Sort sort = new Sort(Sort.Direction.DESC, "createTime");
        Pageable pageable = PageRequest.of(page - 1, size, sort);
        Page<User> all = userDao.listAllUserNoPassword(pageable);
        return ResponseResult.SUCCESS("获取用户列表成功").setData(all);
    }


}
